package com.example.tcp.client;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.util.ReferenceCountUtil;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import java.util.zip.Adler32;
import java.util.zip.Checksum;

public class Response {
    
    public static final byte[] MSG_END_FLAG = new byte[] { (byte) 0xAA, (byte) 0xAA };
    
    /**
     * 定义加密算法
     */
    private static final String ALGORITHM = "desede";

    /**
     * 加密模式: ECB, 填充模式: 不填充
     */
    private static final String TRANSFORMATION = "DESede/ECB/NoPadding";

    /**
     * 3des加密算法,使用ECB模式,不需要IV向量,BCB方式就需要IV向量
     * 
     * @param data 要加密的数据, 注意,如果数据长度不够8的整数倍,则在后面补0xff,直到长度为8的整数倍
     * @return 密文
     * @throws Exception
     */
    public static byte[] des3EncodeECB(byte[] key, byte[] data) throws Exception {
        DESedeKeySpec spec = new DESedeKeySpec(key);
        SecretKeyFactory keyfactory = SecretKeyFactory.getInstance(ALGORITHM);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, keyfactory.generateSecret(spec));
        return cipher.doFinal(data);
    }

    /**
     * ECB解密,,使用ECB模式,不需要IV向量,BCB方式就需要IV向量
     *
     * @param data 要解密的密文
     * @return 明文
     * @throws Exception
     */
    public static byte[] ees3DecodeECB(byte[] key, byte[] data) throws Exception {
        DESedeKeySpec spec = new DESedeKeySpec(key);
        SecretKeyFactory keyfactory = SecretKeyFactory.getInstance(ALGORITHM);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, keyfactory.generateSecret(spec));
        return cipher.doFinal(data);
    }
    
    public static int adler32(ByteBuf buf) {
        Checksum checksum = new Adler32();
        buf.forEachByte(b -> {
            checksum.update(b);
            return true;
        });
        return (int) checksum.getValue();
    }

    
    /**
     * 数据不够8的倍数补0xff
     * @param buf buf
     * @param length 数据长度,注意，这里的长度不一定是buf的长度
     * @return 数据
     */
    private static ByteBuf paddingFF(ByteBuf buf, int length) {
        int size = length % 8;
        if (size == 0) {
            return buf;
        }
        size = 8 - size;
        for (int i = 0; i < size; ++i) {
            buf.writeByte(0xff);
        }
        return buf;
    }

    private static byte[] getKeyByDeviceId(String deviceId) {
        return null;
    }

/**
     * 构建返回帧，注意，这里的有效数据处理完后会调用release
     * @param magic 魔数,帧头标示
     * @param deviceId 设备号
     * @param seqId 流水号
     * @param msgType 消息类型
     * @param effectiveData 有效数据
     * @param logPacket 是否打印帧日志
     * @return 数据帧
     */
    public static ByteBuf buildResponse(int magic, String deviceId, int seqId, int msgType, ByteBuf effectiveData, boolean logPacket) {

        ByteBuf body = null;
        try {
            int effectiveSize = effectiveData.readableBytes(); // 有效数据
            body = ByteBufAllocator.DEFAULT.directBuffer(25 + effectiveSize + 7 + 4 + 2); // 加7主要是为了补0xFF预留
            body.writeShort(magic); // 帧头
            int checksumBeginIndex = body.writerIndex(); // mark
            body.writeShort(0); // 总长度,先占位====================检验值从这里开始
            body.writeBytes(deviceId.getBytes()); // 终端编号
            int encBeginIndex = body.writerIndex(); // mark
            body.writeByte(msgType); // 帧类型================加密数据从这里开始
            body.writeShort(seqId); // 流水号
            body.writeShort(effectiveSize); // 有效数据长度
            body.writeBytes(effectiveData); // 有效数据:成功====================检验值到这里结束
            int checksumEndIndex = body.writerIndex(); // mark
            body.writeInt(0); // 校验,先占位
            int encEndIndex = body.writerIndex(); // mark
            paddingFF(body, encEndIndex - encBeginIndex); // 数据长度必须是8的倍数,不足补0xFF================加密数据到这里结束
            encEndIndex = body.writerIndex(); // 有可能补了0xff数据,所以这里重新计算一下
            body.writeBytes(MSG_END_FLAG); // 帧尾

            body.setShort(body.readerIndex() + 2, body.readableBytes()); // 这里开始把占位值写上 帧长度
            body.setInt(checksumEndIndex,
                    adler32(body.slice(checksumBeginIndex, checksumEndIndex - checksumBeginIndex))); // 检验值

//            if (log.isDebugEnabled() && logPacket) {
//                log.debug("before encoder: {}==>{}", deviceId, ByteBufUtil.hexDump(body));
//            }

            byte[] key = getKeyByDeviceId(deviceId);

            body.setBytes(encBeginIndex,
                    des3EncodeECB(key, ByteBufUtil.getBytes(body, encBeginIndex, encEndIndex - encBeginIndex)));

//            if (log.isDebugEnabled() && logPacket) {
//                log.debug("after encoder: {}<=={}", deviceId, ByteBufUtil.hexDump(body));
//            }

            return body;

        } catch (Throwable e) {
            if (body != null) {
                ReferenceCountUtil.release(body);
            }
//            log.error("buildFrameBody catch exception", e);
            return null;
        } finally {
            ReferenceCountUtil.release(effectiveData);
        }
    }
}